package com.sdu.cloudservice.service.impl;

import com.sdu.cloudservice.dao.ActualPriceDao;
import com.sdu.cloudservice.dao.PredictPriceDao;
import com.sdu.cloudservice.dao.UserDao;
import com.sdu.cloudservice.dto.Envelope;
import com.sdu.cloudservice.entity.ActualPriceEntity;
import com.sdu.cloudservice.entity.PredictPriceEntity;
import com.sdu.cloudservice.service.PredictPriceService;
import com.sdu.cloudservice.util.DateUtil;
import com.sdu.cloudservice.util.PythonUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.collections.map.HashedMap;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.ParseException;
import java.util.*;

/**
 * 19-07-01 lxf
 * @author
 * LXF
 */
@Service
public class PredictPriceServiceImpl implements PredictPriceService {
    public static final String FILENAME = "/home/zekdot/cloudService/script/predict/main.py";
    @Autowired
    PredictPriceDao predictPriceDao;
    @Autowired
    ActualPriceDao actualPriceDao;
    @Autowired
    UserDao userDao;
    @Autowired
    private SqlSession sqlSession;
    //可以直接比较端点的情况-->先把预测之前的这两个点的值求出（如果有的话，结束点视作没有值），在与此时的端点值进行比较-->
    //没有直接插；有：start比较，end比较（代表后面有预测过的值）
    //先将之前的start-end删除，但是生成start，end点的值（如果存在），再插入start-end（比较后）
  /**
     *
     * 调用脚本进行预测
     * @param zoneId 可用区id
     * @param instanceId 实例id
     * @param algType 算法类型
     * @param endDate 最后日期
     * @return
     */
    public List<PredictPriceEntity> predict(String zoneId,String instanceId,int algType,String endDate) throws ParseException {
//        List<PredictPriceEntity> list_test = new ArrayList<>();
//        list_test.add(new PredictPriceEntity(zoneId,instanceId,"2019-07-04T01:10:00Z",1.414,3,0));
//        list_test.add(new PredictPriceEntity(zoneId,instanceId,"2019-07-05T22:50:00Z",1.424,3,1));
//        list_test.add(new PredictPriceEntity(zoneId,instanceId,"2019-07-07T01:10:00Z",1.424,3,2));
//        return list_test;
//        System.out.println("endDate is " + endDate);
        Date startDate;
        try {
            startDate = new Date(DateUtil.aliStr2Date(endDate).getTime() - 600 * 1000);
            endDate = DateUtil.dateTime2AliStr(startDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        List<PredictPriceEntity> list = new ArrayList<>();
//        System.out.println(algType);
//        System.out.println(zoneId);
//        System.out.println(instanceId);
//        System.out.println(endDate);
        JSONArray jsonArray = JSONArray.fromObject(PythonUtil.queryPython3(FILENAME,String.valueOf(algType),zoneId,instanceId,endDate));


//        System.out.println(jsonArray.toString());
        for(Object object:jsonArray){
            JSONObject jsonObject = JSONObject.fromObject(object);
            PredictPriceEntity predictPriceEntity = new PredictPriceEntity();
            predictPriceEntity.setZoneId(zoneId);
            predictPriceEntity.setInstanceId(instanceId);
            predictPriceEntity.setAlgType(algType);
            predictPriceEntity.setTime(jsonObject.getString("time"));
            predictPriceEntity.setSpot(jsonObject.getDouble("spot"));
            predictPriceEntity.setStatus(1);
            list.add(predictPriceEntity);
        }
        PredictPriceEntity lastEntity = list.get(list.size()-1);
        lastEntity.setTime(DateUtil.dateTime2AliStr(new Date(DateUtil.aliStr2Date(lastEntity.getTime()).getTime() + 600 * 1000)));
        list.set(list.size() - 1,lastEntity);
        // list.get(list.size()-1).setTime(DateUtil.dateTime2AliStr(new Date(DateUtil.aliStr2Date(endDate).getTime() - 3*24*60*60*1000)));
        if(list.size()>1){
            list.get(0).setStatus(0);
            list.get(list.size() - 1).setStatus(2);
        }
        return list;
    }

    @Override
    public Envelope getAlgorithm() {
        Map<String,Integer> algMap = new HashedMap();
        algMap.put("LSTM",0);
        algMap.put("线性回归",1);
        return new Envelope(algMap);
    }

    /**
     *[start,end)但仍需bigger，需要确定predict中是否存在足够的predict
     * end的价格需是前一时刻的。predict时端点值比存在。
     * start = max(start,now)
     * start-end<2d，只给now-end，now下取整
     *
     * 改为1+2，baseline: now
     * @param zoneId
     * @param instanceId
     * @return
     */
    @Override
    public Envelope get2DayPredictPrice(String zoneId, String instanceId, int alg_type,String username) throws ParseException {

        Date now = new Date();
        /**
         Date start = DateUtil.aliStr2Date(start_time);
         Date end = DateUtil.aliStr2Date(end_time);
         if (DateUtil.getIntervalSeconds(end,now) >= 0)
         return new Envelope(10,"predict天数越界",null);
         //start和now的比较要大的
         if (DateUtil.getIntervalSeconds(start,now) > 0){//now
         //取整    默认end是整的
         start_time = DateUtil.getNearestPoint(DateUtil.dateTime2AliStr(now));
         }

         //start-end<2d
         if (DateUtil.getIntervalDays(start,end) > 2)
         return new Envelope(9,"predict天数越界",null);
         **/
        String start_time = DateUtil.getNearestPoint(DateUtil.dateTime2AliStr(now));
        now = DateUtil.aliStr2Date(start_time);
        //System.out.println(start_time);
        String end_time = DateUtil.dateTime2AliStr(new Date(now.getTime()+2*24*60*60*1000));
        String predict_end_time = DateUtil.dateTime2AliStr(new Date(now.getTime()+3*24*60*60*1000));
        String last_time = DateUtil.dateTime2AliStr(new Date(now.getTime()-24*60*60*1000));

        List<PredictPriceEntity> future = getPredictPrice(zoneId,instanceId,start_time,end_time,alg_type);
        if (future.isEmpty()||!isFullNew(future,start_time,end_time)){
            try{
                future = predict(zoneId,instanceId,alg_type,start_time);
            }catch (Exception e){
                //e.printStackTrace();
                JSONArray jsonArray = new JSONArray();
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("time",last_time);
                jsonObject.put("origin",0);
                jsonObject.put("spot",0);
                jsonArray.add(jsonObject);
                jsonObject.put("time",end_time);
                jsonArray.add(jsonObject);
                return new Envelope(jsonArray);
            }


            updateDB(zoneId,instanceId,start_time,predict_end_time,alg_type,future);
        }
        future = getPredictPrice(zoneId,instanceId,start_time,end_time,alg_type);
        List<ActualPriceEntity> past = getActualPrice(zoneId,instanceId,last_time,start_time);
        Map<String,List> map = new HashMap<>();
        map.put("past",past);
        map.put("future",future);
        userDao.addQueryTimes(username);
        return new Envelope(map);
    }

    /**
     * 逻辑
     * start-end这三天：
     * 	空
     * 	间断	中间存在两个值一样不包括端点
     * 	不间断	对的
     *
     * 起始点：
     * 	在预测里面	        A
     * 	不在预测里面普通	B
     * 	是上一个预测的结束	C	上次恰好没预测到这里，这里存在的意义是结束标志
     * 起始点值的问题：	找小于等于的值
     * 	A	正确的
     * 	B	前面没有值	null
     * 		前面有值，必为预测结束的值。
     * 	C	为预测结束的值。
     *  为了解决起始点的问题需要得到<=的三个值，来分辨出B2、C（两个值无法区分(1)1|1）
     * 0为空，1，2代表普通的值
     * A: 001,012,123,111,(1)112
     * B    B1: 000
     *      B2: 122
     * C 122
     *
     * 结束点值的问题：	找大于等于的值
     * 	后面没有值，则代表该点还未有预测，返回null
     * 	后面有值，可以直接拿前一个的值给他（start-end）
     * 		前面没有值，整个就是null(留给起始点解决)
     * 		前面有值	结束点，给他也无妨，在检测间断的时候会检测到它前面就间断了
     * 			        正常点，正确的。
     *
     * 需要预测时：
     * 	1.删除start-end
     * 	2.预测
     * 	3.插入	起始	A直接插	该值与前者一样就不插（当作续作）
     * 			        B直接插
     * 			        C	值一样就两个都删 ，再续作
     * 				        不一样就删除之前的结束值，再续作
     *          结束  末尾属于预测过的还是未预测的
     *                  未预测的    直接插
     *                  预测过的    将下一个值与该预测末尾的end比较，相等都删除，不等删除end
     */

    /**
     * 返回近三天的act和predict的对比
     *
     * @param zoneId
     * @param instanceId
     * @param algType
     * @return
     */
    @Override
    public Envelope get3DayActPreDif(String zoneId, String instanceId, int algType, String username) throws ParseException {
        Date now = new Date();
        String end_time = DateUtil.getNearestPoint(DateUtil.dateTime2AliStr(now));
        now = DateUtil.aliStr2Date(end_time);
        String start_time = DateUtil.dateTime2AliStr(new Date(now.getTime()-3*24*60*60*1000));

        List<PredictPriceEntity> predict = getPredictPrice(zoneId,instanceId,start_time,end_time,algType);
//        System.out.println(start_time);
        //空或不满，不满：
        if (predict.isEmpty()||!isFullNew(predict,start_time,end_time)){
            try{
                predict = predict(zoneId,instanceId,algType,start_time);
            }catch (Exception e){
                e.printStackTrace();
                JSONArray jsonArray = new JSONArray();
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("time",start_time);
                jsonObject.put("origin",0);
                jsonObject.put("spot",0);
                jsonArray.add(jsonObject);
                jsonObject.put("time",end_time);
                jsonArray.add(jsonObject);
                return new Envelope(jsonArray);
            }
            updateDB(zoneId,instanceId,start_time,end_time,algType,predict);
        }
        List<ActualPriceEntity> actual = getActualPrice(zoneId,instanceId,start_time,end_time);
        userDao.addQueryTimes(username);
        Map<String,List> map = new HashMap<>();
        map.put("predict",predict);
        map.put("actual",actual);
        return new Envelope(map);
    }

    /**
     * 返回近期的dif,最少给近3天（没有，会预测）
     * actual:  List<ActualPriceEntity>
     * predict: List<List<PredictPriceEntity>>  内层list不会是null
     *
     * @param zoneId
     * @param instanceId
     * @param algType
     * @param username
     * @param days
     * @return
     * @throws ParseException
     */
    @Override
    public Envelope getActPreDif(String zoneId, String instanceId, int algType, String username, int days) throws ParseException {
        if (days<3)
            return new Envelope(233,"会不会选天数？",null);
        Date now = new Date();
        String end_time = DateUtil.getNearestPoint(DateUtil.dateTime2AliStr(now));
        now = DateUtil.aliStr2Date(end_time);
        String start_time0 = DateUtil.dateTime2AliStr(new Date(now.getTime()-3*24*60*60*1000));
        String start_time = DateUtil.dateTime2AliStr(new Date(now.getTime()-days*24*60*60*1000));
        //先判断够不够三天的
        List<PredictPriceEntity> predict = getPredictPrice(zoneId,instanceId,start_time0,end_time,algType);
        //不够都是空,补齐3天的
        if (predict.isEmpty()||!isFullNew(predict,start_time,end_time)){
            predict = predict(zoneId,instanceId,algType,start_time);
            updateDB(zoneId,instanceId,start_time,end_time,algType,predict);
        }
        predict = getPredictPrice(zoneId,instanceId,start_time,end_time,algType);
        List<List<PredictPriceEntity>> predict_divided = new ArrayList<List<PredictPriceEntity>>();
        List<PredictPriceEntity> list = new ArrayList<PredictPriceEntity>();
        for (int i=0; i<predict.size();i++){
            list.add(predict.get(i));
            if (predict.get(i).getStatus()==2){
                predict_divided.add(list);
                list = new ArrayList<PredictPriceEntity>();
            }
        }
        if (!list.isEmpty())
            predict_divided.add(list);
        List<ActualPriceEntity> actual = getActualPrice(zoneId,instanceId,start_time,end_time);
        userDao.addQueryTimes(username);
        Map<String, List> map = new HashMap<>();
        map.put("actual",actual);
        map.put("predict",predict_divided);
        return new Envelope(map);
    }


//    public List<PredictPriceEntity> getPredictPrice(String zoneId, String instanceId, String start_time,String end_time, int alg_type){
//
//        //endTime稍大的time，不直接<是因为可能存在没有预测到的。
//        PredictPriceEntity endTimeBigger = predictPriceDao.getPredictPriceBigger(zoneId,instanceId,end_time,alg_type);
//        if (endTimeBigger==null)
//            return null;
//        //startTime稍小的time
//        List<PredictPriceEntity> startTimeSmaller = predictPriceDao.getPredictPriceSmaller(zoneId,instanceId,start_time,alg_type);
//        if(startTimeSmaller==null)//B1
//            return null;//均返回null，代表不全或没有需要预测
//        if (startTimeSmaller.size()==3){//1为A,2为A
//            if (startTimeSmaller.get(0)==startTimeSmaller.get(1)){//B1,C,A(111)
//                if (startTimeSmaller.get(1)!=startTimeSmaller.get(2))//!A
//                    return null;
//            }
//        }
//        //这样获取只剩下中间断点和结束点(前后有值却是未预测到的空白)
//        List<PredictPriceEntity> predict = predictPriceDao.getPredictPrice(zoneId, instanceId, startTimeSmaller.get(0).getTime(), endTimeBigger.getTime(), alg_type);
//        //修饰断点  start
//        predict.get(0).setTime(start_time);
//        //不要end的值要前一个值
//        predict.get(predict.size()-1).setTime(end_time);
//        predict.get(predict.size()-1).setSpot(predict.get(predict.size()-2).getSpot());
//        return predict;
//    }
//
    //如果有那么end一定存在，而开始可能不存在
    public List<ActualPriceEntity> getActualPrice(String zoneId, String instanceId, String start_time,String end_time){
        ActualPriceEntity start = actualPriceDao.getActualPriceByZoneIdAndInstanceId1(zoneId,instanceId,start_time);
        if(start==null){
           start = actualPriceDao.getActualPriceByZoneIdAndInstanceId2(zoneId,instanceId,start_time);
           if (start==null)
               return null;//代表超出了项目开始时间或数据起始时间
           start.setTime(start_time);
        }
        ActualPriceEntity end = actualPriceDao.getActualPriceByZoneIdAndInstanceId2(zoneId,instanceId,end_time);
        end.setTime(end_time);

        List<ActualPriceEntity> actual = actualPriceDao.getActualPriceByZoneIdAndInstanceId4(zoneId,instanceId,end_time,start_time);
        actual.add(0,start);
        actual.add(end);
        return actual;
    }

    /**
     * 判断list中是否有断点
     * @param list
     * @return
     */
    public boolean isFull(List<PredictPriceEntity> list){
        if (list==null)
            return false;
        double last_spot = list.get(0).getSpot();
        for (int i=1; i<list.size()-1; i++){//最后一个可能是正常的结束，如果不是，在倒数第二个就会结束该算法。
            if (last_spot==list.get(i).getSpot())
                return false;
        }
        return true;
    }

    //不仅要看list是不是个整体，还要看端点是否对。
    public boolean isFullNew(List<PredictPriceEntity> list,String start,String end){
        if (list==null||list.size()==1||list.get(0).getTime()!=start||list.get(list.size()-1).getTime()!=end)
            return false;
        for (int i=0; i<list.size()-1; i++){
            if (list.get(i).getStatus()==2)//如果数据库的predict合乎规范||list.get(i-1).getStatus()>list.get(i).getStatus()不需要
                return false;
        }
        return true;
    }

    //之前start和end提前检查，适用性太差，改为得到值后直接找中间的。
    public List<PredictPriceEntity> getPredictPrice(String zoneId, String instanceId, String start_time,String end_time, int alg_type){
        //start find twice
        PredictPriceEntity start = predictPriceDao.getExactlyPredictPrice(zoneId,instanceId,start_time,alg_type);
        if (start==null)
            start = predictPriceDao.getPredictPriceSmaller(zoneId,instanceId,start_time,alg_type);
        //end same
        PredictPriceEntity end = predictPriceDao.getExactlyPredictPrice(zoneId,instanceId,end_time,alg_type);
        if (end==null)
            end = predictPriceDao.getPredictPriceBigger(zoneId,instanceId,end_time,alg_type);
        List<PredictPriceEntity> predict = predictPriceDao.getPredictPrice(zoneId, instanceId, (start==null?start_time:start.getTime()),
                (end==null?end_time:end.getTime()), alg_type);
//        System.out.println(end);
        //有<=start的，修饰，是否合法给isull
        if (predict.size() > 1) {
            if (start!=null)
                predict.get(0).setTime(start_time);
            //有>=end的
//        System.out.println(new Gson().toJson(predict.get(0)));
            if (end!=null){
                //不要end的值要前一个值  如果end存在
                predict.get(predict.size()-1).setTime(end_time);
                predict.get(predict.size()-1).setSpot(predict.get(predict.size()-2).getSpot());
            }
        }
        else return new ArrayList<>();
        return predict;
    }

    public int insertLot(List<PredictPriceEntity> predict){
        int i = 0;
        if (predict==null)
            return i;
        // 新建一个mapper
        PredictPriceDao mapper = sqlSession.getMapper(PredictPriceDao.class);
        // 遍历所有的预测信息
        for (int j = 0; j < predict.size(); j++) {
            // 取出当前预测值
           PredictPriceEntity predictPriceEntity = predict.get(j);
            // 存储
            try {
                i += mapper.insertIntoPredictPrice(predictPriceEntity.getZoneId(),predictPriceEntity.getInstanceId(),predictPriceEntity.getTime(),
                        predictPriceEntity.getSpot(),predictPriceEntity.getAlgType(),predictPriceEntity.getStatus());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return i;
    }

    public int delete(String zoneId, String instanceId, String start_time,String end_time, int alg_type){
        return predictPriceDao.delPredictPrices(zoneId, instanceId, start_time, end_time, alg_type);
    }

    /**
     * delete->insert>modify
     * start:
     *  start前  null,2  nothing to do
     *           1       和前一个比较是否相等。等删start；不等do nothing
     *           0                                        将start status设为1
     * end:
     *  end后    null,0  nothing to do
     *           !0代表后面有值（续）需要把end点删除
     *           2       与后一个比较是否相等。等后一个作为end；不等需要插入end，spot为end后的，status为1
     *           1       与后一个比较是否相等。等删后一个,并将end status改为1；
     *  end后面
     */
    public void modify(String zoneId, String instanceId, String start_time,String end_time, int alg_type) throws ParseException {
        //start find twice
        PredictPriceEntity start = predictPriceDao.getExactlyPredictPrice(zoneId,instanceId,start_time,alg_type);
        PredictPriceEntity smaller = predictPriceDao.getPredictPriceSmaller(zoneId,instanceId,start_time,alg_type);
        //判断找到的smaller的正确性
        if (smaller!=null) {
            if (start.getSpot() == smaller.getSpot() && smaller.getStatus() != 2)
                predictPriceDao.delPredictPrice(zoneId, instanceId, start_time, alg_type);//del start
            if (start.getSpot() != smaller.getSpot() && smaller.getStatus() == 0)
                predictPriceDao.updateStatus(1,zoneId,instanceId,start_time,alg_type);
        }
        //end same
        PredictPriceEntity end = predictPriceDao.getExactlyPredictPrice(zoneId,instanceId,end_time,alg_type);
        PredictPriceEntity bigger = predictPriceDao.getPredictPriceBigger(zoneId,instanceId,end_time,alg_type);
        //bigger的正确性
        if (bigger!=null) {
            if (bigger.getStatus() == 1){
                predictPriceDao.updateStatus(1,zoneId,instanceId,end_time,alg_type);
                if (end.getSpot() == bigger.getSpot())
                    predictPriceDao.delPredictPrice(zoneId, instanceId, bigger.getTime(), alg_type);
            }
            if (bigger.getStatus() == 2){
                if (end.getSpot() != bigger.getSpot()){
                    predictPriceDao.insertIntoPredictPrice(zoneId,instanceId,end_time,bigger.getSpot(),alg_type,1);
                }
            }
            predictPriceDao.delPredictPrice(zoneId, instanceId, end_time, alg_type);
        }
    }
    @Transactional
    public void updateDB(String zoneId, String instanceId, String start_time,String end_time, int algType,List<PredictPriceEntity> predict) throws ParseException {
        delete(zoneId,instanceId,start_time,end_time,algType);
        insertLot(predict);
        modify(zoneId,instanceId,start_time,end_time,algType);
    }
}
